; 内核打印功能实现
TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0

section .data
put_int_buffer dd 0, 0

[bits 32]
section .text
; put_char，将栈中的一个字符写入光标所在处
global put_char
global put_str
global put_int
global set_cursor

put_int:
    pushad
    mov ebp, esp
    mov eax, [ebp + 4 * 9]
    mov edx, eax
    mov edi, 7
    mov ecx, 8
    mov ebx, put_int_buffer

.16based_4bits:
    and edx, 0x0000000F
    cmp edx, 9
    jg .is_A2F
    add edx, '0'
    jmp .store

.is_A2F:
    sub edx, 10
    add edx, 'A'

.store:
    mov [ebx + edi], dl
    dec edi
    shr eax, 4
    mov edx, eax
    loop .16based_4bits

.ready_print:
    inc edi

.skip_prefix_0:
    cmp edi, 8
    je .full0

.go_on_skip:
    mov cl, [put_int_buffer + edi]
    inc edi
    cmp cl, '0'
    je .skip_prefix_0
    dec edi
    jmp .put_each_num

.full0:
    mov cl, '0'
.put_each_num:
    push ecx
    call put_char
    add esp, 4
    inc edi
    mov cl, [put_int_buffer + edi]
    cmp edi, 8
    jl .put_each_num
    popad
    ret

; 字符串打印函数，基于put_char封装
put_str:
    push ebx
    push ecx
    xor ecx, ecx
    mov ebx, [esp + 12]

.go_on:
    mov cl, [ebx]
    cmp cl, 0
    jz .str_over
    push ecx
    call put_char
    add esp, 4
    inc ebx
    jmp .go_on

.str_over:
    pop ecx
    pop ebx
    ret

put_char:
    pushad              ;备份 32 位寄存器环境
    mov ax, SELECTOR_VIDEO  ;需要保证 gs 中为正确的视频段选择子
    mov gs, ax

; ;-----------获取当前光标位置
    mov dx, 0x03d4      ;索引寄存器
    mov al, 0x0e        ;用于提供光标位置的高8位
    out dx, al
    mov dx, 0x03d5      ;通过读写数据端口 0x3d5 来获得或设置光标位置
    in  al, dx          ;得到了光标位置的高8位
    mov ah, al          ;移动到ah

    mov dx, 0x03d4
    mov al, 0x0f         ;用于提供光标位置的低8位
    out dx, al
    mov dx, 0x03d5
    in al, dx
; ;-----------
    mov bx, ax          ;将光标存入bx
    mov ecx, [esp + 36] ;pushad 压入 4 x 8 = 32 字节，加上主调函数4字节的返回地址，故 esp+36 字节

    cmp cl, 0xd         ;CR 0x0d 回车符, LF 0x0a 换行符
    jz .is_carriage_return
    cmp cl, 0xa
    jz .is_line_feed

    cmp cl, 0x8         ;BS(backspace）退格符的 asc 码是8
    jz .is_backspace
    jmp .put_other

.is_backspace:
    dec bx
    shl bx, 1               ;光标位置×2即为字符在显存中的偏移地址

    mov byte [gs:bx], 0x20
    inc bx                  ;低字节asic码，高字节属性，+1即为属性
    mov byte [gs:bx], 0x07
    shr bx, 1               ;bx/2，即为光标位置
    jmp set_cursor

.put_other:
    shl bx, 1
    mov [gs:bx], cl
    inc bx
    mov byte [gs:bx], 0x07
    shr bx, 1
    inc bx
    cmp bx, 2000
    jl set_cursor

.is_line_feed:
.is_carriage_return:
    xor dx, dx
    mov ax, bx
    mov si, 80

    div si

    sub bx, dx

.is_carriage_return_end:
    add bx, 80
    cmp bx, 2000
.is_line_feed_end:
    jl set_cursor

.roll_screeen:
    cld
    mov ecx, 960

    mov esi, 0xc00b80a0
    mov edi, 0xc00b8000
    rep movsd

    mov ebx, 3840
    mov ecx, 80

.cls:
    mov word [gs:ebx], 0x0720
    add ebx, 2
    loop .cls
    mov bx, 1920

set_cursor:
;将光标设为bx值
;;;;;;先设置高8位;;;;;;
    mov dx, 0x03d4
    mov al, 0x0e
    out dx, al
    mov dx, 0x03d5
    mov al, bh
    out dx, al
;;;;;;再设置低8位;;;;;;
    mov dx, 0x03d4
    mov al, 0x0f
    out dx, al
    mov dx, 0x03d5
    mov al, bl
    out dx, al

.put_char_done:
    popad
    ret

global cls_screen
cls_screen:
   pushad
   ;;;;;;;;;;;;;;;
	; 由于用户程序的cpl为3,显存段的dpl为0,故用于显存段的选择子gs在低于自己特权的环境中为0,
	; 导致用户程序再次进入中断后,gs为0,故直接在put_str中每次都为gs赋值.
   mov ax, SELECTOR_VIDEO	       ; 不能直接把立即数送入gs,须由ax中转
   mov gs, ax

   mov ebx, 0
   mov ecx, 80*25
 .cls:
   mov word [gs:ebx], 0x0720		  ;0x0720是黑底白字的空格键
   add ebx, 2
   loop .cls
   mov ebx, 0

 .set_cursor:				  ;直接把set_cursor搬过来用,省事
;;;;;;; 1 先设置高8位 ;;;;;;;;
   mov dx, 0x03d4			  ;索引寄存器
   mov al, 0x0e				  ;用于提供光标位置的高8位
   out dx, al
   mov dx, 0x03d5			  ;通过读写数据端口0x3d5来获得或设置光标位置
   mov al, bh
   out dx, al

;;;;;;; 2 再设置低8位 ;;;;;;;;;
   mov dx, 0x03d4
   mov al, 0x0f
   out dx, al
   mov dx, 0x03d5
   mov al, bl
   out dx, al
   popad
   ret